<?php
/*
 * @Author: your name
 * @Date: 2020-12-12 16:50:31
 * @LastEditTime: 2021-01-15 17:10:55
 * @LastEditors: Please set LastEditors
 * @Description: 原生规则上传
 * @FilePath: /special/application/api/service/netfilter/UploadSourceFirewallRuleService.php
 */
namespace app\api\service\netfilter;

use app\adminapiv3\service\module_manage\netfilter\FirewallSetService;
use app\adminapiv3\service\module_manage\netfilter\SortRuleService;
use app\common\database\AutoIncreMentService;
use app\common\definition\HostDefinition;
use app\common\definition\netfilter\FirewallRuleDefinition;
use app\common\exception\CustomException;
use app\common\traits\CommonServiceTrait;
use think\Db;

class UploadSourceFirewallRuleService extends FirewallRuleUploadService
{
    use CommonServiceTrait;
    /**
     * @name: 上传原生规则(接管防火墙时上传的规则)
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-10 14:24:54
     */
    public function upload ()
    {
        if($this->firstUpload())
        {
            return [];
        }

        $data = Db::name('netfilter_firewall_rule')->where('host_id', $this->host_id)->order('rank', 'ASC')->select();

        //添加日志规则做匹配规则用
        $SortRuleService = new SortRuleService();
        $rule_a = $SortRuleService->addLogRule($this->host_id, $data);
        $rule_a = $SortRuleService->addDisguisePortRule($this->host_id, $this->host_os, $rule_a);
        //获取需要添加的防火墙规则同时添加原生规则
        $add_firewall_a = $this->getAddFirewallRule($rule_a);
        //删除防火墙规则
        $delete_id_a = [];
        $delete_source_firewall_a = $this->getDeleteFirewallRule($rule_a, $delete_id_a);
        //修改防火墙规则 添加新增的 删除未应用成功的
        $this->resetFirewallRule($delete_source_firewall_a, $delete_id_a, $add_firewall_a);
        //修改日志
        $preview_content = Db::name('netfilter_firewall_rule')->where('host_id', $this->host_id)->order('rank', 'ASC')->select();
        Db::name('netfilter_firewall_rule_log')->where('id', $this->preview_content_id)->update(
            [
                'preview_content' => json_encode($preview_content)
            ]
        );
        //修改防火墙默认行为
        $this->modifyFirewallConfig();
    }

    /**
     * @name: 需要添加规则的通信规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-12 17:10:49
     */
    private function getConnectRule ()
    {
        $connect_rule_count = Db::name('netfilter_safe_rule')->where('host_id', $this->host_id)->count();
        if ($connect_rule_count > 0)
        {
            return [];
        }
        $temp = $this->host_os == HostDefinition::LINUX_OPERATION_SYSTEM ? $this->linuxConnectRule() : $this->windowsConnectRule();
        $increase_number = count($temp);
        //获取安全规则自增id
        $inserId = AutoIncreMentService::getTableAutoId('netfilter_safe_rule', $increase_number);

        $return = [];
        foreach ($temp as $rule)
        {
            $return[] = [
                'id' => $inserId,
                'host_id' => $this->host_id,
                'content' => $rule,
                'status' => FirewallRuleDefinition::RULE_ENABLE,
            ];
            $inserId++;
        }

        return $return;
    }

    /**
     * @name: linux 防火墙规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-09 11:20:53
     */
    private function linuxConnectRule ()
    {
        //获取代理信息
        $linuxProxyRule = $this->getProxyInformation();
        //获取客户端通信信息
        $contact = Db::name('HostContact')->field('server_ip,port')->whereIn('host_id', $this->host_id)->where('is_contact', 1)->column('server_ip,port','host_id');
        
        $return = [
            '-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT',
            '-A INPUT -p icmp -j ACCEPT',
            '-A INPUT -i lo -j ACCEPT',
            '-A OUTPUT -j ACCEPT',
        ];

        empty($contact) ? : $return[] = '-A INPUT -s ' . $contact['server_ip'] . '/32 -p tcp -m state --state NEW -m tcp --dport '. $contact['port'] . ' -j ACCEPT';

        foreach ($linuxProxyRule as $ip => $port)
        {
            $port = array_unique($port);
            $return[] = '-A INPUT -s ' . $ip . '/32 -p tcp -m state --state NEW -m multiport --sports '. implode(',', $port) . ' -j ACCEPT';
        }

        return $return;
    }

    /**
     * @name: windows通信规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-10 09:32:58
     */
    private function windowsConnectRule ()
    {
        $return = [
            'netsh advfirewall firewall add rule name="icmpv6in" dir=in action=allow enable=yes profile=public,private,domain protocol=58',
            'netsh advfirewall firewall add rule name="icmpv6out" dir=out action=allow enable=yes profile=public,private,domain protocol=58',
            'netsh advfirewall firewall add rule name="icmpv4in" dir=in action=allow enable=yes profile=public,private,domain protocol=1',
            'netsh advfirewall firewall add rule name="icmpv4out" dir=out action=allow enable=yes profile=public,private,domain protocol=1',
        ];

        return $return;
    }

    /**
     * @name: 获取主机代理信息
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-10 09:51:10
     */
    private function getProxyInformation ()
    {
        $return = [];

        //获取代理信息
        $proxyData = Db::name('ServerProxy')->field('ip,type,port')->order('port', 'ASC')->select();
        $filter = [
            'mysql'
        ];
        
        foreach ($proxyData as $proxy)
        {
            if (!in_array($proxy['type'], $filter))
            {
                $return[$proxy['ip']][] = $proxy['port'];
            }
        }

        return $return;
    }

    /**
     * @name: 获取防火墙默认行为规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-12 17:18:40
     */
    private function setLinuxFirewallSetRule()
    {
        $firewall_set_rule = array_splice($this->upload_content, 0 ,3);
        
        foreach ($firewall_set_rule as $rank => $rule)
        {
            preg_match('/-p\s+(input|output|forward)\s+(accept|drop)/i', strtolower($rule), $rule_a);
            if (empty($rule_a))
            {
                throw new CustomException('Linux防火墙默认处理方式规则错误', 422);
            }

            $this->upload_firewall_set[$rule_a[1]] = $rule_a[2] == 'accept' ? 1 : 0;
            $rank += 1;
            $this->upload_firewall_set_rule[] = [
                'rule_id' => $rank,
                'host_id' => $this->host_id,
                'content' => $rule,
                'rank' => $rank,
                'source' => FirewallRuleDefinition::FIREWALL_SET_RULE_SOURCE,
                'region' => FirewallRuleDefinition::FIREWALL_DEFAULT_CONFIG_RULE_REGION,
            ];
        }

        //当出站/入站/转发规则为 accept 并且 没有其他规则那么 防火墙默认处理方式为关闭
        $this->upload_firewall_set['main_switch'] = count($this->upload_content) == 0 && ($this->upload_firewall_set['input'] && $this->upload_firewall_set['output'] && $this->upload_firewall_set['forward']) ? 0 : 1;
    }

    /**
     * @name: 获取windows设置规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-15 18:30:14
     */
    private function getWindowsFirewallSetRule ($state_rule, $defeult_rule, &$firewall_set)
    {
        $return = [];
        foreach ($state_rule as $rule)
        {
            $return[] = $rule;
            preg_match('/netsh advfirewall set (private|public|domain)profile state (on|off)/i', $rule, $rule_a);
            $firewall_set[$rule_a[1].'_main_switch'] = $rule_a[2] == 'on' ? 1 : 0;
        }
        
        foreach ($defeult_rule as $rule)
        {
            $return[] = $rule;
            preg_match('/netsh advfirewall set (private|public|domain)profile firewallpolicy (block|allow)inbound,(allow|block)outbound/i', $rule, $rule_a);
            $firewall_set[$rule_a[1].'_input'] = $rule_a[2] == 'allow' ? 1 : 0;
            $firewall_set[$rule_a[1].'_output'] = $rule_a[3] == 'allow' ? 1 : 0;
        }
        
        return $return;
    }

    /**
     * @name: 当客户端规则是第一次上传时 执行的操作
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-12 17:52:36
     */
    private function firstUpload ()
    {
        //定义防火墙规则和通信规则容器
        $firewall_rule = [];
        //获取需要添加的防火墙通信规则
        $safe_rule = $this->getConnectRule();

        //当通信规则为空时说明不是第一次上传 所以返回
        if (empty($safe_rule))
        {
            return false;
        }

        if ($this->host_os == HostDefinition::LINUX_OPERATION_SYSTEM)
        {
            $this->setLinuxFirewallSetRule();
        }
         
        $firewall_rule = $this->upload_firewall_set_rule;
        $init_rank = count($firewall_rule);
        foreach ($safe_rule as $rule)
        {
            $firewall_rule[] = [
                'rule_id' => $rule['id'],
                'host_id' => $this->host_id,
                'content' => $rule['content'],
                'rank' => $init_rank++,
                'source' => FirewallRuleDefinition::CONNECT_RULE_SOURCE,
                'region' => FirewallRuleDefinition::CONNECT_NORMAL_RULE_REGION,
            ];
        }
        $increase_number = count($this->upload_content);
        $origin_rule_id = AutoIncreMentService::getTableAutoId('host_initial_rule', $increase_number);
        $source_rule = [];
        foreach ($this->upload_content as $rule)
        {   
            $temp_source = [
                'id' => $origin_rule_id,
                'host_id' => $this->host_id,
                'content' => $rule,
                'status' => FirewallRuleDefinition::RULE_ENABLE,
            ];
            $is_add = true;
            foreach ($firewall_rule as $f_rule)
            {
                if ($f_rule['content'] == $rule)
                {
                    $temp_source['status'] = FirewallRuleDefinition::RULE_DISABLE;
                    $is_add = false;
                    break;
                }
            }
            $source_rule[] = $temp_source;
            if($is_add)
            {
                $firewall_rule[] = [
                    'rule_id' => $origin_rule_id,
                    'host_id' => $this->host_id,
                    'content' => $rule,
                    'rank' => $init_rank++,
                    'source' => FirewallRuleDefinition::ORIGIN_RULE_SOURCE,
                    'region' => FirewallRuleDefinition::UNKNOW_RULE_REGION,
                ];
            }
            $origin_rule_id ++; 
        }

        Db::startTrans();
        try {
            Db::name('netfilter_safe_rule')->insertAll($safe_rule);
            Db::name('netfilter_firewall_rule')->insertAll($firewall_rule);
            Db::name('host_initial_rule')->insertAll($source_rule);
            //修改防火墙默认行为
            $this->modifyFirewallConfig();
            $this->preview_content = Db::name('netfilter_firewall_rule')->where('host_id', $this->host_id)->select();
            $this->archivedLog();
            Db::commit();
        } catch (\Exception $e) {
            throw new CustomException($e->getMessage(), 422);
            Db::rollback();
        }
        
        if ($this->host_os == HostDefinition::LINUX_OPERATION_SYSTEM)
        {
            $SortRuleService = new SortRuleService();
            $SortRuleService->pushContentToFile($this->host_id);
        }

        $link = $this->checkOnline();
        $errmessage = $this->sendToClient($link, 'downloadRules');

        if($errmessage['err_id'] > 0)
        {
            throw new CustomException('消息下发失败:'.$errmessage['err_msg'], 422);
        }

        return true;
    }

    /**
     * @name: 检测主机是否在线
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-14 15:05:42
     */
    function checkOnline ()
    {
        $link = Db::name('host_link')->where('host_id', $this->host_id)->where('module', 'netfilter')->find();
        if(empty($link))
        {
            throw new CustomException('缺少主机连接信息', 422);
        }

        if($link['status'] != HostDefinition::LINK_ONLINE)
        {
            throw new CustomException('主机已离线无法发送规则信息', 422);
        }

        return $link;
    }

    /**
     * @name: 修改防火墙默认行为配置
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2021-01-05 17:15:47
     */
    private function modifyFirewallConfig ()
    {
        if($this->host_os == HostDefinition::LINUX_OPERATION_SYSTEM)
        {
            Db::name('netfilter_firewall_config_linux')->where('host_id', $this->host_id)->update($this->upload_firewall_set);
        }
        else
        {
            Db::name('netfilter_firewall_config_windows')->where('host_id', $this->host_id)->update($this->upload_firewall_set);
        }
    }
}